package mya_dc.master_server;

import static java.awt.SystemTray.getSystemTray;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Component;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.Queue;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.TitledBorder;
import javax.swing.table.TableColumn;
import com.swtdesigner.FocusTraversalOnArray;

import mya_dc.master_server.database.GUIdata;
import mya_dc.shared_classes.gui.CenterCellRenderer;
import mya_dc.shared_classes.MYA_Authentication;
import mya_dc.shared_classes.SharedDefinitions;
import mya_dc.shared_classes.gui.AboutWindow;
import mya_dc.shared_classes.gui.MultiLabel;
import mya_dc.shared_classes.gui.SharedComponents;

/**
 * Implements user interface for the Slave program
 *
 * @author      Yuval Shimron
 * <br>			MYA
 */
public class MasterServerGUI
{
	// GUI shared elements 
	private JLabel 		slavesPortLabel;
	private JLabel 		compilationServersPortLabel;
	private JLabel 		clientsPortLabel;
	private JButton 	m_DeleteSelected;
	private JButton 	m_DeleteAll;
	private JTable 		slavesTable;
	private JTable 		clientsTable;
	private JTable 		compServTable;
	private JFrame 		frame;
	public JTextField 	slavesListenPort;
	public JTextField	clientsListenPort;
	public JTextField 	serversListenPort;
	private JButton 	m_connectButton;
	private JButton 	m_abortButton;
	private JButton 	m_exitButton;
	private JLabel 		m_label;
	public JCheckBox 	m_notifyCheckBox;
	public JCheckBox 	m_minimizeOnConnectionCheckBox;
	private MultiLabel 	m_Log;
	private boolean 	m_bMinimized;
	private boolean 	m_bCanceled;
	private TrayIcon	m_icon;
	private GUIdata     m_GUIdata;
	private Calendar    m_Calendar = Calendar.getInstance();
	private FileOutputStream m_outStreamLog;
	private String 		m_LogFileName;
	private JScrollPane scrollPaneLog;
	private JPanel 		panel_1;
	private JPanel 		panel_2;
	private JPanel 		panel;
	
	// Reference to parent class in order to call it's methods
	private MasterServer m_Parent;
	
	// Queue is used to allow listeners to send 
	// notification messages to appear in system tray
	public NotifyQueue m_NotifyQueue;
	
	// Queue is used to allow listeners to send messages to appear in log tab
	//public LogQueue m_LogQueue;

	/**
	 * Create the application
	 * 
	 * @param Parent - MasterServer caller class
	 */
	public MasterServerGUI(MasterServer Parent)
	{
		new File(System.getProperty("user.dir")+"\\logs").mkdirs();
		m_LogFileName = System.getProperty("user.dir")+MasterServer.LOG_FILE_NAME;
		m_Parent = Parent;
		m_NotifyQueue = new NotifyQueue();
		initialize();
		ImportData();
		toLog("hello world\n" + time() + "Master server created", true);
		try {
			m_outStreamLog = new FileOutputStream(m_LogFileName,true);
		} catch (IOException e) {
			m_outStreamLog = null;
		}
	}
	
	/**
	 * Imports last used user data from "GUI_masterServerData.bin"
	 */
	private void ImportData()
	{
		try
		{
			String filename = System.getProperty("user.dir") + "\\gui\\GUI_masterServerData.bin";
			ObjectInputStream inStream = new ObjectInputStream(new FileInputStream(filename));
			m_GUIdata = (GUIdata)inStream.readObject();
			inStream.close();
			sync();
			//System.out.println("GUI Data loaded successfully");
		}
		catch(Exception e)	{}
	}
	
	/**
	 * Exports last used user data to "GUI_masterServerData.bin"
	 */
	private void ExportData()
	{		
		try
		{
			new File(System.getProperty("user.dir") + "\\gui\\").mkdirs();
			String filename = System.getProperty("user.dir") + "\\gui\\GUI_masterServerData.bin";
			m_GUIdata = new GUIdata(this);
			ObjectOutputStream outStream = new ObjectOutputStream(new FileOutputStream(filename));
			outStream.writeObject(m_GUIdata);		
			outStream.flush();
			outStream.close();
			//System.out.println("GUI Data saved successfully");
		}
		catch(Exception e) {}
	}

	/**
	 * Synchronize data from m_GUIdata (saved user selections from last run)
	 */
	public void sync()
	{
		Runnable doWorkRunnable = new Runnable() {
		    public void run() { 
		    	slavesListenPort.setText(m_GUIdata.sSlavesort);
		    	clientsListenPort.setText(m_GUIdata.sClientsPort);
		    	serversListenPort.setText(m_GUIdata.sServersPort);
				m_minimizeOnConnectionCheckBox.setSelected(m_GUIdata.bMinimize);
				m_notifyCheckBox.setSelected(m_GUIdata.bNotify);
		    }
		};
		SwingUtilities.invokeLater(doWorkRunnable);	
	}
	
	/**
	 * Sets visibleness of UI
	 * @param b - true if visible
	 */
	public void setVisible(boolean b)
	{
		frame.setVisible(b);
	}
		
	/**
	 * Initialize the contents of the frame
	 */
	private void initialize() {
		try 
		{
			UIManager.setLookAndFeel(
		    "com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
		}
		catch(Exception e) { }
		frame = new JFrame();
		frame.getContentPane().setFocusCycleRoot(true);
		frame.setBounds(100, 100, 800, 620);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.getContentPane().setLayout(null);
		frame.setTitle("MYA-DC Master Server");
		
		frame.addWindowListener(new WindowAdapter() {			
			public void windowClosed(final WindowEvent arg0) {
				try {
					if (m_connectButton.getText().equals("Trying..."))
					{
						// terminate the connection
						m_abortButton.doClick();
						Thread.sleep(1000); // allow 1 second to terminate
					}
					if (m_connectButton.getText().equals("Terminate!"))
					{
						m_connectButton.doClick();
						Thread.sleep(1000); // allow 1 second to terminate
					}
				}
				catch (Exception e) {}
				ExportData();
				m_Parent.terminate();
				try { Thread.sleep(2000);} catch (Exception e) {} // allow 2 seconds to safe terminate
				try { if (m_outStreamLog != null) m_outStreamLog.close(); } catch (IOException e) {}
				System.exit(0);
			}
		});
		
		m_exitButton = new JButton();
		m_exitButton.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent arg0) {
				frame.dispose();	
			}
		});
		m_exitButton.setText("Exit");
		m_exitButton.setBounds(670, 525, 106, 27);
		frame.getContentPane().add(m_exitButton);
		
		// maximization and size change disabling:
		frame.setResizable(false); 
		// minimization support:	
		if (SystemTray.isSupported()) {
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			m_icon = new TrayIcon(SharedComponents.getImage(SharedDefinitions.Entity.ENTITY_MASTER),
				"MYA-DC Main Server - running");
			m_icon.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					m_bMinimized = false;
					frame.setVisible(true);
					frame.setExtendedState(JFrame.NORMAL);
					getSystemTray().remove(m_icon);
				}
			});
	    
			frame.addWindowListener(new WindowAdapter() {
				@Override
				public void windowIconified(WindowEvent e) {
					m_bMinimized = true;
					frame.setVisible(false);
					try {
						getSystemTray().add(m_icon);
					} catch (AWTException e1) {
						e1.printStackTrace();
					}
					if (m_notifyCheckBox.isSelected())
					{
						m_icon.displayMessage("Information", "MYA-DC Master Server is running in background",
						TrayIcon.MessageType.INFO);
					}
				}
			});
		}

		// tabbed pane to display servers, slaves, clients and log
		final JTabbedPane tabbedPane = new JTabbedPane();
		tabbedPane.setBounds(20, 140, 755, 375);
		frame.getContentPane().add(tabbedPane);
		final JScrollPane scrollPane = new JScrollPane();
		tabbedPane.addTab("Compilation Servers", null, scrollPane, null);
		compServTable = new JTable();
		compServTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		/*compServTable.addMouseListener(new MouseAdapter() {
			public void mouseClicked(final MouseEvent e) {
				int row = compServTable.getSelectedRow();
				Long ID = (Long)compServTable.getValueAt(row, 0);
				if (!m_Parent.isServerConnected(ID))
				{
					m_DeleteSelected.setEnabled(true);
				}
			}
		});*/
		UpdateServersTables();
		scrollPane.setViewportView(compServTable);
		final JScrollPane scrollPane_2 = new JScrollPane();
		tabbedPane.addTab("Compilation Slaves", null, scrollPane_2, null);
		slavesTable = new JTable();
		slavesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		UpdateSlavesTables();
		scrollPane_2.setViewportView(slavesTable);
		
		final JScrollPane scrollPane_1 = new JScrollPane();
		tabbedPane.addTab("Clients", null, scrollPane_1, null);
		clientsTable = new JTable();
		clientsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		clientsTable.addMouseListener(new MouseAdapter() {
			public void mouseClicked(final MouseEvent e) {
				m_DeleteSelected.setEnabled(true);
			}
		});
		UpdateClientsTables();
		scrollPane_1.setViewportView(clientsTable);
		
		scrollPaneLog = new JScrollPane();
		tabbedPane.addTab("  Log  ", null, scrollPaneLog, null);
		m_Log = new MultiLabel(null,
				MultiLabel.LEADING,MultiLabel.LEADING,MultiLabel.LEADING);
		scrollPaneLog.setViewportView(m_Log);
		
		clientsTable.setModel(m_Parent.m_ClientsArray);
		compServTable.setModel(m_Parent.m_CompilationServers);
    	slavesTable.setModel(m_Parent.m_SlavesArray); 	
    	centerCells(clientsTable);
    	centerCells(compServTable);
    	centerCells(slavesTable);	
		
		tabbedPane.addMouseListener(new MouseAdapter() {
			public void mouseClicked(final MouseEvent e)
			{
				if (tabbedPane.getSelectedIndex() == 2)
				{
					if (!m_Parent.m_ClientsArray.isEmpty())
						m_DeleteAll.setEnabled(true);
					m_DeleteAll.setToolTipText("Delete all Clients from database");
					compServTable.clearSelection();
					slavesTable.clearSelection();
				}
				else if (tabbedPane.getSelectedIndex() == 0)
				{
					m_DeleteAll.setEnabled(false);
					m_DeleteAll.setToolTipText("\"Delete All\" is not supported for Compilation Servers");
					clientsTable.clearSelection();
					slavesTable.clearSelection();
				}
				else
				{
					m_DeleteAll.setToolTipText("\"Delete All\" is not supported for Slaves");
					m_DeleteAll.setEnabled(false);
					compServTable.clearSelection();
					clientsTable.clearSelection();
				}
				
				m_DeleteSelected.setEnabled(false);	
			}
		});

		// deletes all (clients) from table and database
		m_DeleteAll = new JButton();
		m_DeleteAll.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e)
			{
				if (tabbedPane.getSelectedIndex() == 2)
					m_Parent.deleteAllUsers();
			}
		});
		m_DeleteAll.setText("Delete All");
		m_DeleteAll.setBounds(150, 525, 106, 27);
		m_DeleteAll.setEnabled(false);
		frame.getContentPane().add(m_DeleteAll);

		// delete selected row client / not-connected-server from DB
		m_DeleteSelected = new JButton();
		m_DeleteSelected.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e)
			{
				try {
					if (tabbedPane.getSelectedIndex() == 2)
					{
						int row = clientsTable.getSelectedRow();
						String name = (String)clientsTable.getValueAt(row, 1);
						String pass = (String)clientsTable.getValueAt(row, 2);
						String proj = (String)clientsTable.getValueAt(row, 3);
				
						MYA_Authentication auth = new MYA_Authentication(name, pass, proj);
						m_Parent.deleteUser(auth);
					}
				}
				catch(Exception ee) {}
			}
		});
		m_DeleteSelected.setText("Delete Selected");
		m_DeleteSelected.setBounds(20, 525, 123, 27);
		m_DeleteSelected.setEnabled(false);
		frame.getContentPane().add(m_DeleteSelected);

		panel = new JPanel();
		panel.setLayout(null);
		panel.setBorder(new TitledBorder(null, "", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
		panel.setBounds(25, 20, 252, 102);
		frame.getContentPane().add(panel);
		
		clientsPortLabel = new JLabel();
		clientsPortLabel.setBounds(20, 17, 126, 17);
		panel.add(clientsPortLabel);
		clientsPortLabel.setText("Listen Port for Clients");

		compilationServersPortLabel = new JLabel();
		compilationServersPortLabel.setBounds(20, 42, 142, 17);
		panel.add(compilationServersPortLabel);
		compilationServersPortLabel.setText("Listen Port for Servers");
		
		slavesPortLabel = new JLabel();
		slavesPortLabel.setBounds(20, 67, 126, 17);
		panel.add(slavesPortLabel);
		slavesPortLabel.setText("Listen Port for Slaves");
		clientsListenPort = new JTextField();
		clientsListenPort.setBounds(160, 15, 69, 21);
		panel.add(clientsListenPort);
		clientsListenPort.setToolTipText("Clients listening port");
		serversListenPort = new JTextField();
		serversListenPort.setBounds(160, 40, 69, 21);
		panel.add(serversListenPort);
		serversListenPort.setToolTipText("Compilation servers listening port");
		slavesListenPort = new JTextField();
		slavesListenPort.setBounds(160, 65, 69, 21);
		panel.add(slavesListenPort);
		slavesListenPort.setToolTipText("Slaves listening port");

		panel_2 = new JPanel();
		panel_2.setBorder(new TitledBorder(null, "", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
		panel_2.setLayout(null);
		panel_2.setBounds(443, 20, 324, 102);
		frame.getContentPane().add(panel_2);

		m_minimizeOnConnectionCheckBox = new JCheckBox();
		m_minimizeOnConnectionCheckBox.setBounds(25, 10, 248, 25);
		panel_2.add(m_minimizeOnConnectionCheckBox);
		m_minimizeOnConnectionCheckBox.setText("Minimize to tray on successful listening");

		m_notifyCheckBox = new JCheckBox();
		m_notifyCheckBox.setBounds(25, 40, 248, 25);
		panel_2.add(m_notifyCheckBox);
		m_notifyCheckBox.setText("Show notifications when minimized");

		// information label (updates on-line)
		m_label = new JLabel();
		m_label.setBounds(30, 70, 269, 17);
		panel_2.add(m_label);
		m_label.setText("Main Server is Idle");
		m_label.setForeground(Color.blue);

		panel_1 = new JPanel();
		panel_1.setBorder(new TitledBorder(null, "", TitledBorder.DEFAULT_JUSTIFICATION, TitledBorder.DEFAULT_POSITION, null, null));
		panel_1.setLayout(null);
		panel_1.setBounds(290, 20, 140, 102);
		frame.getContentPane().add(panel_1);
				
		m_connectButton = new JButton();
		m_connectButton.setBounds(17, 20, 106, 27);
		panel_1.add(m_connectButton);
		m_connectButton.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent arg0) {
				// connection button has 3 states:
				// (1) "Initialize!": Idle. waiting to listen on ports and accept connections.
				// (2) "Trying...": Trying to connect
				// (3) "Terminate!": Connection accepted. May accept in background.
				if (m_connectButton.getText().equals("Initialize!"))
				{
					m_connectButton.setEnabled(false);
					ExportData();
					setPortLabelsStatus(false);
					m_bCanceled = false;
					m_connectButton.setText("Trying...");
					m_label.setForeground(Color.blue);
					m_label.setText("Establishing main server connectivity...");
					m_abortButton.setEnabled(true);
				
					int iCompServsPort;
					int iClientsPort;
					int iSlavesPort;
					try {
						iCompServsPort = Integer.parseInt( serversListenPort.getText() );
						iClientsPort = Integer.parseInt( clientsListenPort.getText() );
						iSlavesPort = Integer.parseInt( slavesListenPort.getText() );		
					}
					catch (Exception e)
					{
						m_connectButton.setText("Initialize!");
						SharedComponents.MessageBox(frame, SharedComponents.EMessageBoxType.ERROR_ILLEGALEPORTS, 0);
						m_connectButton.setEnabled(true);
						m_abortButton.setEnabled(false);
						m_label.setText("Main Server is Idle");
						setPortLabelsStatus(true);
						return;
					}
					
					try {
						m_Parent.createSockets(iClientsPort, iCompServsPort, iSlavesPort);
					}
					catch (Exception e)
					{
						m_connectButton.setText("Initialize!");
						SharedComponents.MessageBox(frame, SharedComponents.EMessageBoxType.ERROR_CANTLISTENPORTS, 0);
						m_connectButton.setEnabled(true);
						m_abortButton.setEnabled(false);
						m_label.setText("Main Server is Idle");
						setPortLabelsStatus(true);
						return;
					}
					
					m_Parent.createThreads();
					m_Parent.runThreads();

					if (!m_bCanceled)
					{
						m_connectButton.setText("Terminate!");	
						m_connectButton.setEnabled(true);
						m_label.setForeground(Color.blue);
						m_label.setText("Main server initialized successfully!");
						m_abortButton.setEnabled(false);
						if (m_minimizeOnConnectionCheckBox.isSelected() && SystemTray.isSupported())
						{
							frame.setExtendedState(JFrame.ICONIFIED); 
						}
					}
					toLog("Master server initialized successfully", true);
				}
				else if (m_connectButton.getText().equals("Terminate!"))
				{
					m_connectButton.setEnabled(false);
					setPortLabelsStatus(true);
					m_Parent.terminate();
					m_abortButton.setEnabled(false);
					m_label.setForeground(Color.blue);
					m_label.setText("Main Server is Idle");
					toLog("Master server initialization terminated", true);
					setPortLabelsStatus(true);
					m_connectButton.setText("Initialize!");
					m_connectButton.setEnabled(true);
				}
			}	
		});
		m_connectButton.setText("Initialize!");
		
		// user can cancel the connection if he/she clicks cancel, if the connection process is not done yet.
		m_abortButton = new JButton();
		m_abortButton.setBounds(17, 55, 106, 27);
		panel_1.add(m_abortButton);
		m_abortButton.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				if (m_connectButton.getText().equals("Trying..."))
				{	
					m_bCanceled = true;
					m_abortButton.setEnabled(false);
					
					// calls parent to terminate sockets and save-data thread
					m_Parent.terminate();
					
					// reset buttons:
					m_connectButton.setText("Initialize!");
					m_connectButton.setEnabled(true);
					m_label.setForeground(Color.blue);
					m_label.setText("Disconnected from Server");
					toLog("Master server initialization aborted", true);					
					setPortLabelsStatus(true);
				}
			}
		});
		m_abortButton.setText("Cancel");
		m_abortButton.setEnabled(false);
    	frame.getContentPane().setFocusTraversalPolicy(new FocusTraversalOnArray(new Component[] {panel, panel_2, panel_1}));

		final JMenuBar menuBar = new JMenuBar();
		frame.setJMenuBar(menuBar);

		final JMenu fileMenu = new JMenu();
		fileMenu.setText("File");
		menuBar.add(fileMenu);

		final JMenuItem exportDbMenuItem = new JMenuItem();
		exportDbMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				doExport();
			}
		});
		exportDbMenuItem.setText("Export DB");
		fileMenu.add(exportDbMenuItem);

		final JMenuItem importDbMenuItem = new JMenuItem();
		importDbMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				doImport();
			}
		});
		importDbMenuItem.setText("Import DB");
		fileMenu.add(importDbMenuItem);

		final JMenu helpMenu = new JMenu();
		helpMenu.setText("Help");
		menuBar.add(helpMenu);

		final JMenuItem helpMenuItem = new JMenuItem();
		helpMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				SharedDefinitions.openHelpFile(frame);
			}
		});
		helpMenuItem.setText("Help");
		helpMenu.add(helpMenuItem);

		final JMenuItem aboutMenuItem = new JMenuItem();
		aboutMenuItem.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				AboutWindow.showAbout(frame);
			}
		});
		aboutMenuItem.setText("About");
		helpMenu.add(aboutMenuItem);
	}

	private void setPortLabelsStatus(boolean status)
	{
		slavesListenPort.setEnabled(status);
		clientsListenPort.setEnabled(status);
		serversListenPort.setEnabled(status);
	}
	private String time() {
		return m_Calendar.getTime().toString() + "> ";
	}

	private void doImport()
	{
		String path = SharedComponents.getFileWithExtension("Load",frame, "bin", "MYA DC - Master server database");
		if (path==null)
			return;
		try
		{
			m_Parent.loadData(path);
			toLog("Loaded DB file: " + path + " Succesfully!", true);
			clientsTable.setModel(m_Parent.m_ClientsArray);
			compServTable.setModel(m_Parent.m_CompilationServers);
			UpdateClientsTables();
			UpdateServersTables();
		}
		catch (Exception e)
		{
			toLog("Unable to load file named: " + path, true);
		}
		
	}
	
	private void doExport()
	{
		String path = SharedComponents.getFileWithExtension("Save", frame, "bin", "MYA DC - Master server database");
		if (path==null)
			return;
		//add extension if needed
		int dotPos = path.lastIndexOf(".");
		if (dotPos == -1 )
		{
			path += ".bin";
		}
		else
		{
			String extension = path.substring(dotPos+1);
			if (!(extension.equals("bin")))
			{
				path += ".bin";
			}
		}
		try
		{
			m_Parent.saveData(path);
			toLog("Saved DB file: " + path + " Succesfully!", true);
		}
		catch (Exception e)
		{
			toLog("Unable to save file named: " + path, true);
		}
	}
	
	/**
	 * Updates Servers Table from m_Parent.m_CompilationServers
	 * 
	 */
	public synchronized void UpdateServersTables()
	{
		Runnable doWorkRunnable = new Runnable() {
		    public void run() { 
				compServTable.updateUI();
		    	centerCells(compServTable);
		    }
		};
		SwingUtilities.invokeLater(doWorkRunnable);	
	}
	
	/**
	 * Updates Slaves Table from m_Parent.m_SlavesArray
	 * 
	 */
	public synchronized void UpdateSlavesTables()
	{
		Runnable doWorkRunnable = new Runnable() {
		    public void run() { 
		    	slavesTable.updateUI();
		    	centerCells(slavesTable);
		    }
		};
		SwingUtilities.invokeLater(doWorkRunnable);	
	}
		
	/**
	 * Updates Clients Table from m_Parent.m_ClientsArray
	 * 
	 */
	public synchronized void UpdateClientsTables()
	{
		Runnable doWorkRunnable = new Runnable() {
		    public void run() { 
				clientsTable.updateUI();
				clientsTable.validate();
				centerCells(clientsTable);
		    }
		};
		SwingUtilities.invokeLater(doWorkRunnable);	
	}
	
	
	/**
	 * centers cells in a table
	 * 
	 * @param table
	 */
	void centerCells(JTable table)
	{
		TableColumn tableColumn = null;
		for (int i = 0 ; i < table.getColumnCount() ; i++)
		{
			tableColumn = table.getColumnModel().getColumn(i);
			tableColumn.setCellRenderer(new CenterCellRenderer());;
		}
	}
	
	/**
	 * Sends notifications to log tab and to tray if minimized and m_notifyCheckBox is checked
	 * 
	 */
	public synchronized void SendNotification(String header, String message, TrayIcon.MessageType type)
	{
		m_NotifyQueue.addNotification(new Notification(header, message, type));
		Notify();
	}
	
	private void toLog(String message, boolean addTime)
	{
		synchronized (m_Log) {
			String toLog = (addTime ? time() : "") + message;
			if (m_outStreamLog != null) try {
				m_outStreamLog.write(toLog.getBytes());
				m_outStreamLog.write("\r\n".getBytes());
			} catch (IOException e) {}
			m_Log.addText(toLog);
			m_Log.repaint();
		}
		scrollPaneLog.updateUI();
	}
	
	/**
	 * Calls invokeLater to send all messages in queue to tray and log tab
	 * 
	 */
	private synchronized void Notify()
	{
    	Runnable doWorkRunnable = new Runnable() {
		    public void run() { 
		    	Notification current;
		    	synchronized (m_NotifyQueue) {
		    		while (!m_NotifyQueue.m_Queue.isEmpty())
			    	{
		    			String strCurrent = null;
			    		current = m_NotifyQueue.m_Queue.remove();
			    		
			    		if (strCurrent == null) {
			    			strCurrent = time() + current.message;
			    		}
			    		else {
			    			strCurrent += "\n" + time() + current.message;
			    		}
						toLog(strCurrent, false);
						
			    		if (m_bMinimized && SystemTray.isSupported() && m_notifyCheckBox.isSelected())
						{
							m_icon.displayMessage(current.header, current.message, current.type);
						}
			    	}
				}	
		    }
		};
		SwingUtilities.invokeLater(doWorkRunnable);
	}
	

	
	/**
	 * Inner class to represent a message
	 * 
	 */
	private class Notification
	{
		/**
		 * Constructor
		 * 
		 * @param rheader - header message text
		 * @param rmessage - message body
		 * @param rtype - message type
		 */
		public Notification(String rheader, String rmessage, TrayIcon.MessageType rtype)
		{
			header = rheader;
			message = rmessage;
			type = rtype;
		}
		
		public String header;
		public String message;
		public TrayIcon.MessageType type;
	}
	
	/**
	 * Inner class to accumulate notifications
	 * 
	 */
	private class NotifyQueue
	{	
		public Queue<Notification> m_Queue;
		
		public NotifyQueue()
		{
			m_Queue = new LinkedList<Notification>();
		}
		
		/**
		 * Add a notification
		 * 
		 * @param message
		 */public synchronized void addNotification(Notification message)
		{
			m_Queue.add(message);
		}
		
		/**
		 * Returns a notification
		 */
		public synchronized Notification getNotification()
		{
			return m_Queue.remove();
		}
	}
}
